﻿using Extented.UI.Core.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Extented.UI.Core.Native
{
    public class FrameworkRenderElementContext : IFrameworkRenderElementContext, ISupportInitialize
    {
        public static readonly object DataContextPropertyKey;
        public static readonly object ForegroundPropertyKey;
        public static readonly object IsEnabledPropertyKey;
        public static readonly object IsTouchEnabledPropertyKey;
        public static readonly object FlowDirectionPropertyKey;
        protected internal FREInheritedProperties InheritedProperties;
        Visibility? visibility;
        double? opacity;
        Size renderSize;
        HorizontalAlignment? ha;
        VerticalAlignment? va;
        Thickness? margin;
        bool isMouseOverCore;
        bool? showBounds;
        readonly Locker isInSupportInitialize = new Locker();
        bool isMeasureValid = true;
        bool isArrangeValid = true;
        Dock? dock;
        static FrameworkRenderElementContext()
        {
            FlowDirectionPropertyKey = FREInheritedProperties.Register(null, (frec, ov, nv) => frec.OnFlowDirectionChanged((FlowDirection?)ov, (FlowDirection?)nv), (frec, v) => frec.OnFlowDirectionChangingCore((FlowDirection?)v));
            IsTouchEnabledPropertyKey = FREInheritedProperties.Register(false, (frec, ov, nv) => frec.IsTouchEnabledChanged((bool)ov, (bool)nv));
            IsEnabledPropertyKey = FREInheritedProperties.Register(true, (frec, ov, nv) => frec.OnIsEnabledChanged((bool)ov, (bool)nv), (frec, v) => frec.OnIsEnabledChangingCore((bool)v));
            DataContextPropertyKey = FREInheritedProperties.Register(propertyChangedCallback: (frec, ov, nv) => frec.ResetTriggers());
            ForegroundPropertyKey = FREInheritedProperties.Register(propertyChangedCallback: (frec, ov, nv) => frec.OnForegroundChanged(ov, nv));
        }
        public FlowDirection? FlowDirection
        {
            get { return (FlowDirection?)InheritedProperties.GetValue(FlowDirectionPropertyKey); }
            set { InheritedProperties.SetValue(FlowDirectionPropertyKey, value); }
        }
        public bool IsMouseOverCore
        {
            get { return isMouseOverCore; }
            set { SetProperty(ref isMouseOverCore, value, FREInvalidateOptions.None, OnIsMouseOverCoreChanged); }
        }
        public Visibility? Visibility
        {
            get { return visibility; }
            set { SetProperty(ref visibility, value, FREInvalidateOptions.UpdateSubTree); }
        }
        public double? Opacity
        {
            get { return opacity; }
            set { SetProperty(ref opacity, value, FREInvalidateOptions.AffectsChildrenCaches | FREInvalidateOptions.AffectsVisual | FREInvalidateOptions.AffectsOpacity); }
        }
        protected internal double ActualOpacity
        {
            get { return opacity ?? Factory.Opacity; }
        }
        public object DataContext
        {
            get { return InheritedProperties.GetValue(DataContextPropertyKey); }
            set { InheritedProperties.SetValue(DataContextPropertyKey, value); }
        }
        public Brush Foreground
        {
            get { return (Brush)InheritedProperties.GetValue(ForegroundPropertyKey); }
            set { InheritedProperties.SetValue(ForegroundPropertyKey, value); }
        }
        public bool IsEnabled
        {
            get { return (bool)InheritedProperties.GetValue(IsEnabledPropertyKey); }
            set { InheritedProperties.SetValue(IsEnabledPropertyKey, value); }
        }
        public bool IsTouchEnabled
        {
            get { return (bool)InheritedProperties.GetValue(IsTouchEnabledPropertyKey); }
            protected internal set { InheritedProperties.SetValue(IsTouchEnabledPropertyKey, value); }
        }
        public Dock? Dock
        {
            get { return dock; }
            set { SetProperty(ref dock, value); }
        }
        public HorizontalAlignment? HorizontalAlignment
        {
            get { return ha; }
            set { SetProperty(ref ha, value); }
        }
        public VerticalAlignment? VerticalAlignment
        {
            get { return va; }
            set { SetProperty(ref va, value); }
        }
        public Thickness? Margin
        {
            get { return margin; }
            set { SetProperty(ref margin, value); }
        }
        public bool? ShowBounds
        {
            get { return showBounds; }
            set { SetProperty(ref showBounds, value); }
        }
        public bool IsArrangeValid { get { return isArrangeValid; } }
        public bool IsInSupportInitialize { get { return isInSupportInitialize.IsLocked; } }
        public virtual bool AttachToRoot { get { return false; } }
        public bool IsAttachedToRoot { get; set; }
        public Transform RenderTransform { get; protected set; }
        public Geometry LayoutClip { get; internal set; }
        public string Name { get { return Factory.Name; } }
        public Size DesiredSize { get; internal set; }
        public Size RenderSize
        {
            get { return renderSize; }
            set { SetProperty(ref renderSize, value, FREInvalidateOptions.None, RenderSizeChanged); }
        }
        public Vector VisualOffset { get; internal set; }
        public bool NeedsClipBounds { get; internal set; }
        public Rect RenderRect { get { return new Rect(VisualOffset.X, VisualOffset.Y, RenderSize.Width, RenderSize.Height); } }
        public FrameworkRenderElement Factory { get; private set; }
        public string ThemeInfo { get { return Factory.ThemeInfo; } }
        public FrameworkRenderElementContext(FrameworkRenderElement factory)
        {
            Factory = factory;
            InheritedProperties = new FREInheritedProperties(this);
        }
        public void PreApplyTemplate()
        {
            if (IsInSupportInitialize)
                return;
            SetIsMeasureValid();
            SetIsArrangeValid();
        }
        public void Measure(Size availableSize)
        {
            Factory.Measure(availableSize, this);
        }
        public void Arrange(Rect finalRect)
        {
            Factory.Arrange(finalRect, this);
        }
        public void Render(DrawingContext dc)
        {
            Factory.Render(dc, this);
        }
        public void InvalidateMeasure()
        {
            ElementHost.InvalidateMeasure();
        }
        public void InvalidateArrange()
        {
            ElementHost.InvalidateArrange();
        }
        public void InvalidateVisual()
        {
            ElementHost.InvalidateVisual();
        }
        FrameworkRenderElementContext IFrameworkRenderElementContext.GetRenderChild(int index)
        {
            return GetRenderChild(index);
        }
        int IFrameworkRenderElementContext.RenderChildrenCount { get { return RenderChildrenCount; } }
        protected virtual int RenderChildrenCount { get { return 0; } }
        public INamescope Namescope { get; internal set; }
        public IElementHost ElementHost { get; internal set; }
        public FrameworkRenderElementContext Parent { get; private set; }
        protected virtual FrameworkRenderElementContext GetRenderChild(int index)
        {
            return null;
        }
        public virtual void AddChild(FrameworkRenderElementContext child)
        {
            child.Parent = this;
            child.InheritedProperties.CoerceValue();
        }
        public virtual void RemoveChild(FrameworkRenderElementContext child)
        {
            child.Release();
            child.Parent = null;
        }
        protected internal virtual void AttachToVisualTree(FrameworkElement root)
        {
        }
        protected internal virtual void DetachFromVisualTree(FrameworkElement root)
        {
        }
        protected void SetProperty<TProperty>(ref TProperty container, TProperty value, FREInvalidateOptions invalidateOptions = FREInvalidateOptions.UpdateLayout, Action action = null)
        {
            if (object.Equals(container, value))
                return;
            container = value;
            UpdateLayout(invalidateOptions);
            action.Do(x => x());
        }
        public void UpdateLayout(FREInvalidateOptions invalidateOptions = (FREInvalidateOptions.AffectsMeasure | FREInvalidateOptions.AffectsVisual | FREInvalidateOptions.AffectsRenderCaches))
        {
            if (invalidateOptions.HasFlag(FREInvalidateOptions.AffectsMeasure))
                ElementHost.InvalidateMeasure();
            if (invalidateOptions.HasFlag(FREInvalidateOptions.AffectsArrange))
                ElementHost.InvalidateArrange();
            if (invalidateOptions.HasFlag(FREInvalidateOptions.AffectsVisual))
                ElementHost.InvalidateVisual();
            if (invalidateOptions.HasFlag(FREInvalidateOptions.AffectsRenderCaches))
            {
                SetIsArrangeInvalid();
                ResetRenderCaches();
            }
            if (invalidateOptions.HasFlag(FREInvalidateOptions.AffectsGeneralCaches))
            {
                SetIsMeasureInvalid();
                ResetTemplatesAndVisuals();
            }
            if (invalidateOptions.HasFlag(FREInvalidateOptions.AffectsChildrenCaches))
            {
                for (int i = 0; i < RenderChildrenCount; i++)
                {
                    var child = GetRenderChild(i);
                    child.UpdateLayout(invalidateOptions);
                }
            }
            if (invalidateOptions.HasFlag(FREInvalidateOptions.AffectsOpacity))
            {
                UpdateOpacity();
            }
        }
        public virtual bool ShouldUseTransform()
        {
            useMirrorTransform = ShouldUseMirrorTransform();
            return !VisualOffset.X.AreClose(0d) || !VisualOffset.Y.AreClose(0d) || useMirrorTransform;
        }
        public virtual bool ShouldUseMirrorTransform()
        {
            var actualFd = FlowDirection ?? System.Windows.FlowDirection.LeftToRight;
            var parentFd = (Parent == null || !Parent.FlowDirection.HasValue) ? System.Windows.FlowDirection.LeftToRight : Parent.FlowDirection.Value;
            var chromeValue = GetChromeFlowDirectionIfRoot() ?? System.Windows.FlowDirection.LeftToRight;
            if (Parent == null)
                parentFd = chromeValue;
            return Parent != null && actualFd != parentFd;
        }
        protected virtual void UpdateOpacity() { }
        bool useMirrorTransform = false;
        public virtual void UpdateRenderTransform()
        {
            MatrixTransform transform = null;
            if (useMirrorTransform)
                transform = new MatrixTransform(new Matrix(-1, 0, 0, 1, RenderSize.Width + VisualOffset.X, VisualOffset.Y));
            else
                transform = new MatrixTransform(new Matrix(1, 0, 0, 1, VisualOffset.X, VisualOffset.Y));
            transform.Freeze();
            RenderTransform = transform;
        }
        void ResetTriggers()
        {
            if (Namescope.Triggers == null)
                return;
            foreach (var trigger in Namescope.Triggers)
                trigger.Reset();
        }
        protected virtual void RenderSizeChanged() { }
        void ResetRenderCaches()
        {
            if (isInSupportInitialize)
                return;
            ResetRenderCachesInternal();
        }
        protected virtual void ResetRenderCachesInternal()
        {
        }
        void ResetTemplatesAndVisuals()
        {
            if (isInSupportInitialize)
                return;
            ResetTemplatesAndVisualsInternal();
        }
        protected virtual void ResetTemplatesAndVisualsInternal()
        {
            if (!AttachToRoot)
                Namescope.RemoveChild(this);
            else
                Namescope.AddChild(this);
        }
        protected internal virtual void ResetSizeSpecificCaches()
        {
            LayoutClip = null;
        }
        protected internal void ResetRenderTransform()
        {
            RenderTransform = null;
        }
        public void Release()
        {
            if (Namescope.Triggers != null)
            {
                foreach (var trigger in Namescope.Triggers)
                    trigger.Detach();
            }
            for (int i = 0; i < RenderChildrenCount; i++)
            {
                var child = GetRenderChild(i);
                child.Release();
            }
            Namescope.ReleaseElement(this);
            Namescope = null;
            ElementHost = null;
        }
        public void SetValue(string propertyName, object value)
        {
            SetValueOverride(propertyName, value);
        }
        readonly Locker resetValueLocker = new Locker();
        public void ResetValue(string propertyName)
        {
            resetValueLocker.DoLockedActionIfNotLocked(() => ResetValueOverride(propertyName));
        }
        public object GetValue(string propertyName)
        {
            return GetValueOverride(propertyName);
        }
        protected virtual void SetValueOverride(string propertyName, object value)
        {
            if (object.Equals(GetValue(propertyName), value))
                return;
            RenderTriggerHelper.SetConvertedValue(this, propertyName, value);
        }
        protected virtual void ResetValueOverride(string propertyName)
        {
            bool hasMatch = false;
            foreach (var trigger in Namescope.Triggers)
            {
                if (!trigger.Matches(this, propertyName))
                    continue;
                if (trigger is SettableRenderTriggerContextBase && !((SettableRenderTriggerContextBase)trigger).IsValid())
                    continue;
                hasMatch = true;
                trigger.Invalidate();
            }
            if (!hasMatch)
                ResetValueCore(propertyName);
        }
        protected virtual void ResetValueCore(string propertyName)
        {
            RenderTriggerHelper.SetConvertedValue(this, propertyName, null);
        }
        protected virtual object GetValueOverride(string propertyName)
        {
            return RenderTriggerHelper.GetValue(this, propertyName);
        }
        protected virtual void OnForegroundChanged(object oldValue, object newValue) { }
        bool OnIsEnabledChangingCore(bool value)
        {
            value = OnIsEnabledChanging(value);
            if (Parent != null)
            {
                if (!Parent.IsEnabled)
                    value = false;
            }
            else
            {
                var chrome = ElementHost.Parent as Chrome;
                if (chrome != null)
                {
                    if (Equals(this, chrome.Root) && !chrome.isEnabledInternal)
                        value = false;
                }
            }
            return value;
        }
        protected virtual bool OnIsEnabledChanging(bool value) { return value; }
        protected virtual void OnIsEnabledChanged(bool oldValue, bool newValue) { }
        FlowDirection? OnFlowDirectionChangingCore(FlowDirection? value)
        {
            value = OnFlowDirectionChanging(value);
            if (value == null && Parent == null)
            {
                value = GetChromeFlowDirectionIfRoot();
            }
            return value;
        }
        private FlowDirection? GetChromeFlowDirectionIfRoot()
        {
            var chrome = ElementHost.Parent as Chrome;
            if (chrome != null)
            {
                if (Equals(this, chrome.Root) && chrome.flowDirectionInternal == System.Windows.FlowDirection.RightToLeft)
                    return System.Windows.FlowDirection.RightToLeft;
            }
            return null;
        }
        protected virtual FlowDirection? OnFlowDirectionChanging(FlowDirection? value) { return value; }
        protected virtual void OnFlowDirectionChanged(FlowDirection? oldValue, FlowDirection? newValue)
        {
            UpdateLayout(FREInvalidateOptions.AffectsVisual);
        }
        public void CoerceValue(object propertyKey)
        {
            InheritedProperties.CoerceValue(propertyKey);
        }
        public virtual bool HitTest(Point point)
        {
            return new Rect(RenderSize).Contains(point);
        }
        void IFrameworkRenderElementContext.OnMouseMove(MouseRenderEventArgs args) { OnMouseMove(args); }
        void IFrameworkRenderElementContext.OnMouseEnter(MouseRenderEventArgs args) { IsMouseOverCore = true; OnMouseEnter(args); }
        void IFrameworkRenderElementContext.OnMouseLeave(MouseRenderEventArgs args) { IsMouseOverCore = false; OnMouseLeave(args); }
        void IFrameworkRenderElementContext.OnMouseDown(MouseRenderEventArgs args) { OnMouseDown(args); }
        void IFrameworkRenderElementContext.OnMouseUp(MouseRenderEventArgs args) { OnMouseUp(args); }
        void IFrameworkRenderElementContext.OnPreviewMouseDown(MouseRenderEventArgs args) { OnPreviewMouseDown(args); }
        void IFrameworkRenderElementContext.OnPreviewMouseUp(MouseRenderEventArgs args) { OnPreviewMouseUp(args); }
        protected virtual void OnIsMouseOverCoreChanged() { }
        protected virtual void OnMouseMove(MouseRenderEventArgs args) { }
        protected virtual void OnMouseEnter(MouseRenderEventArgs args) { }
        protected virtual void OnMouseLeave(MouseRenderEventArgs args) { }
        protected virtual void OnMouseDown(MouseRenderEventArgs args) { }
        protected virtual void OnMouseUp(MouseRenderEventArgs args) { }
        protected virtual void OnPreviewMouseDown(MouseRenderEventArgs args) { }
        protected virtual void OnPreviewMouseUp(MouseRenderEventArgs args) { }
        public void BeginInit()
        {
            isInSupportInitialize.Lock();
            for (int i = 0; i < RenderChildrenCount; i++)
            {
                ISupportInitialize child = GetRenderChild(i);
                child.BeginInit();
            }
            BeginInitInternal();
        }
        protected virtual void BeginInitInternal()
        {
        }
        public void EndInit()
        {
            isInSupportInitialize.Unlock();
            for (int i = 0; i < RenderChildrenCount; i++)
            {
                ISupportInitialize child = GetRenderChild(i);
                child.EndInit();
            }
            EndInitInternal();
        }
        protected virtual void EndInitInternal()
        {
            if (!isMeasureValid)
                ResetTemplatesAndVisuals();
            if (!isArrangeValid)
                ResetRenderCaches();
        }
        void SetIsMeasureValid()
        {
            isMeasureValid = true;
        }
        void SetIsMeasureInvalid()
        {
            isMeasureValid = false;
        }
        void SetIsArrangeValid()
        {
            isArrangeValid = true;
        }
        void SetIsArrangeInvalid()
        {
            isArrangeValid = false;
        }
        protected virtual void IsTouchEnabledChanged(bool oldValue, bool newValue)
        {
            UpdateTouchState(Namescope, newValue);
        }
        protected void UpdateTouchState(INamescope scope, bool newValue)
        {
            scope.GoToState(newValue ? "Touch" : "NonTouch");
        }
    }
}
